/* Name: main.c
 * Project: hid-custom-rq example
 * Author: Christian Starkjohann
 * Creation Date: 2008-04-07
 * Tabsize: 4
 * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH
 * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
 * This Revision: $Id$
 */

/*
This example should run on most AVRs with only little changes. No special
hardware resources except INT0 are used. You may have to change usbconfig.h for
different I/O pins for USB. Please note that USB D+ must be the INT0 pin, or
at least be connected to INT0 as well.
We assume that an LED is connected to port B bit 0. If you connect it to a
different port or bit, change the macros below:
*/
#define LED_PORT_DDR        DDRB
#define LED_PORT_OUTPUT     PORTB
#define LED_BIT             0

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>  /* for sei() */
#include <util/delay.h>
#include <avr/pgmspace.h>   /* required by usbdrv.h */

#include "uart.h"
// #include "cbuf.h"
#include "iolgt8f0xa.h"
#include "usbdrv.h"
#include "oddebug.h"        /* This is also an example for using debug macros */
#include "requests.h"       /* The custom request numbers we use */
#include "wireless.h"
#include "cbuf.h"
#include "ircode.h"

#define RF_STATUS_LED_ON()		_CLR_IO(LED_PORT, LED_PIN)


extern uint8_t PTX_STATUS;
extern cbuf_t ptxUTxBuffer;
extern cbuf_t ptxURxBuffer;

extern void RTCInit(int periodSecond);
// extern void ADCInit(void);
static void enable_ext_clock(void);
static void disableSWDD(void);

typedef enum {
	USB_STATE_READ_RADIO,
	USB_STATE_WRITE_RADIO,
	USB_STATE_IR_CONFIG,
	USB_STATE_IR_WDATA,
	USB_STATE_IDLE,
} usb_state_t;

// #define USB_ASYNC_BUF_SIZEB		32

static usb_state_t usbState = USB_STATE_IDLE;
// static int usbDataAddr = 0;
static int usbDataLength = 0;
static int usbDataBufferIndex = 0;
static char usbDataBuffer[48];
static uchar usbCtrlReqRet = 0;
static int radioLastError = 0;
static int irDataLen = 0;
static int usbwIndexRec;
static int irDataReady = 0;

//typedef struct {
//	int dirty;
//	usb_state_t state;
//	int start_addr;
//	int data_len;
//} usbctrl_async_msg_t;

// static usbctrl_async_msg_t asyncMsg = {0};

// extern cbuf_t ptxUTxBuffer;
// extern cbuf_t ptxURxBuffer;
/* ------------------------------------------------------------------------- */
void delay_ms(volatile int t) {
	//t *= 1.25;
	while (t --) {
		_delay_ms(1);
	}
}

usbMsgLen_t usbFunctionSetup(uchar data[8])
{
	usbRequest_t *rq = (void *)data;
	
	if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_VENDOR){
		usbMsgPtr = &usbCtrlReqRet;
		
		switch( (u2w_request_t)rq->bRequest & 0x7F) {
			case U2WRQ_RADIO_WRITE:
				usbState = USB_STATE_WRITE_RADIO;
				usbDataLength = rq->wValue.word;
				usbDataBufferIndex = 0;
				return USB_NO_MSG;
			case U2WRQ_RADIO_READ:
				usbState = USB_STATE_READ_RADIO;
				return USB_NO_MSG;
			case U2WRQ_RADIO_RESULT:
				usbCtrlReqRet = radioLastError;
				return 1;
			case U2WRQ_RADIO_RCOUNT:
				usbCtrlReqRet = getDataCount(&ptxURxBuffer);
				return 1;
			case U2WRQ_RADIO_REGRW:
				if (rq->bRequest & 0x80) {
					// CE pin must be low for now
					wirelessWriteReg(rq->wIndex.word, rq->wValue.word);
				}				
				usbCtrlReqRet = wirelessReadReg(rq->wIndex.word);
				return 1;
			case U2WRQ_IO8_RW:
				if (rq->bRequest & 0x80) {
					_SFR_IO8(rq->wIndex.bytes[0]) = rq->wValue.bytes[0];
				}
				usbCtrlReqRet = _SFR_IO8(rq->wIndex.bytes[0]); 
				return 1;
			case U2WRQ_IO8_BIT:
				// kprintf("0x%x:0x%x", rq->wIndex.bytes[0], rq->wValue.bytes[0]);
				if (rq->bRequest & 0x80) {
					_SFR_IO8(rq->wIndex.bytes[0]) |= rq->wValue.bytes[0];
				} else {
					_SFR_IO8(rq->wIndex.bytes[0]) &= ~(rq->wValue.bytes[0]);
				}
				usbCtrlReqRet = _SFR_IO8(rq->wIndex.bytes[0]);
				return 1;
			case U2WRQ_MCU_RESET:
				if (rq->wIndex.word == 0x8579) {
					wdt_enable(WDTO_15MS);
					while (1);
				}
			case U2WRQ_MEM8_RW:
				if (rq->bRequest & 0x80) {
					_SFR_MEM8(rq->wIndex.bytes[0]) = rq->wValue.bytes[0];
				}
				usbCtrlReqRet = _SFR_MEM8(rq->wIndex.bytes[0]);
				return 1;
			case U2WRQ_MEM8_BIT:
				if (rq->bRequest & 0x80) {
					_SFR_MEM8(rq->wIndex.bytes[0]) |= rq->wValue.bytes[0];
					} else {
					_SFR_MEM8(rq->wIndex.bytes[0]) &= ~(rq->wValue.bytes[0]);
				}
				usbCtrlReqRet = _SFR_MEM8(rq->wIndex.bytes[0]);
				return 1;
			case U2WRQ_IR_CONFIG:
				usbState = USB_STATE_IR_CONFIG;
				usbDataLength = rq->wValue.word;
				usbwIndexRec = rq->wIndex.word;
				usbDataBufferIndex = 0;
				return USB_NO_MSG;
			case U2WRQ_IR_WDATA:
				usbState = USB_STATE_IR_WDATA;
				usbDataLength = rq->wValue.word;
				irDataLen = rq->wValue.word;
				usbDataBufferIndex = 0;
				irDataReady = 0;
				return USB_NO_MSG;
			default:
				return 0;
		};
    } else {
        /* calss requests USBRQ_HID_GET_REPORT and USBRQ_HID_SET_REPORT are
         * not implemented since we never call them. The operating system
         * won't call them either because our descriptor defines no meaning.
         */
    }
    return 0;   /* default for not implemented requests: return no data back to host */
}

/* ------------------------------------------------------------------------- */
#if (USB_CFG_IMPLEMENT_FN_READ)
uchar   usbFunctionRead(uchar *data, uchar len) {
	if(usbState == USB_STATE_READ_RADIO) {
		return CBufRead(&ptxURxBuffer, data, len);
	}
	return 0;
}
#endif

/* ------------------------------------------------------------------------- */
#if (USB_CFG_IMPLEMENT_FN_WRITE)
uchar   usbFunctionWrite(uchar *data, uchar len) {		
	memcpy(&usbDataBuffer[usbDataBufferIndex], data, len);
	
	usbDataBufferIndex += len;
	usbDataLength -= len;
	
	if (usbDataLength == 0) {
		if (usbState == USB_STATE_WRITE_RADIO) {
			radioLastError = wirelessWriteSync(usbDataBuffer, usbDataBufferIndex);
		} else if (usbState == USB_STATE_IR_CONFIG) {
			if(IRConfig(usbDataBuffer, usbwIndexRec) != 0) {
				return 0;
			}
		} else if (usbState == USB_STATE_IR_WDATA) {
			//kprintf("bits=%d", usbwIndexRec);
			//IRWrite(usbDataBuffer, usbwIndexRec);
			irDataReady = 20;
		}
	}
	return len;
}
#endif

int main(void)
{
	uchar   i;
	unsigned int index;
	unsigned char bits;
	unsigned char delayMs;
	
	cli();
	wdt_disable();
	
	// clear the WDT overfloat bit
	if(MCUSR & (1 << WDRF)) {
		MCUSR &= ~(1 << WDRF);
	}

	// enable the extern 16Mhz clock source
	enable_ext_clock();
	disableSWDD();
	
	// light the LED
	_CONFIG_IO_OUTPUT(LED_PORT, LED_PIN);
	_CONFIG_IO_OUTPUT(IRTX_PORT, IRTX_PIN);
	_SET_IO(IRTX_PORT, IRTX_PIN);
	
	RF_STATUS_LED_ON();
	
	// open uart for debug
	uart1_open(115200);
	kprintf("system start\r\n");
	
	//IRMain();
	// e2promTest();
	// enable the nRF24L01
	// for PRX device, wait for wireless commands
	// for PTX device, wait for usb commands
	if (wirelessInit() == SYS_ROLE_PRX) {
		//kprintf("remote device\r\n");
		sei();
		wdt_enable(WDTO_1S);
		
		// process wireless commands here
		while (1) {
			wirelessPoll();
			wdt_reset();
		}
	}
	//kprintf("usb device\r\n");

	// enable the ADC and RTC for battery monitor
	// RTCInit(2);
	// ADCInit();
	//cli();
	//memset(buffer, 0x85, sizeof(buffer));
	//e2promWrite(16, buffer, 16);
	//e2promRead(16, buffer, 16);
	//for(i = 0; i < 16; i ++) {
		//kprintf("0x%x ", buffer[i]);
	//}	
	wdt_enable(WDTO_2S);
    /* Even if you don't use the watchdog, turn it off here. On newer devices,
     * the status of the watchdog (on/off, period) is PRESERVED OVER RESET!
     */
    /* RESET status: all port bits are inputs without pull-up.
     * That's the way we need D+ and D-. Therefore we don't need any
     * additional hardware initialization.
     */

	// enable the system interrupt
	// sei();
	//while (1) {
	//	wirelessPoll();	
	//	wdt_reset();
	//}
	
    odDebugInit();
    DBG1(0x00, 0, 0);       /* debug output: main starts */
    usbInit();
    usbDeviceDisconnect();  /* enforce re-enumeration, do this while interrupts are disabled! */
    i = 0;
    while(--i){             /* fake USB disconnect for > 250 ms */
        wdt_reset();
        _delay_ms(1);
    }
    usbDeviceConnect();
    
	// LED_PORT_DDR |= _BV(LED_BIT);   /* make the LED bit an output */
    
	sei();
    DBG1(0x01, 0, 0);       /* debug output: main loop starts */
    for(;;){                /* main event loop */
#if 0   /* this is a bit too aggressive for a debug output */
        DBG2(0x02, 0, 0);   /* debug output: main loop iterates */
#endif
        wdt_reset();
        usbPoll();
		//wirelessPoll();
		if(irDataReady) {
			irDataReady -= 1;
			
			if(irDataReady == 0) {
				irDataReady = 0;
				//cli();
				wdt_disable();
				
				index = 0;
				do {
					bits = usbDataBuffer[index];
					delayMs = usbDataBuffer[index + 1];
					//kprintf("%d, %d\r\n", bits, delayMs);
					IRWrite(&usbDataBuffer[index + 2], bits);
					delay_ms(delayMs);
					
					index += (2 + bits / 8 + ((bits % 8) ? 1 : 0));
				} while (index < irDataLen);
				wdt_enable(WDTO_2S);	
			}
			//sei();			
		}
    }
	return 0;
}

static void enable_ext_clock() {
	// set PC0 and PC1 input and disable the pull-up
	CONFIG_IO_HIZ(C, (1 << 1) | (1 << 0));
	
	_delay_ms(10);
	
	// clock source = external
	PMCR &= ~(1 << 6);
	PMCR |= (1 << 5);
	
	// clock div = 1
	CLKPR = 0x80;
	CLKPR = 0x00;
	
	_delay_ms(10);
}

static void disableSWDD(void) {
	MCUCR = 0x80;
	MCUCR = 0x80;
}

/* ------------------------------------------------------------------------- */
